package ags.muse.radar;

import static robocode.util.Utils.normalRelativeAngle;
import ags.muse.base.Rules;
import ags.muse.base.actors.GunActor;
import ags.muse.base.actors.MovementActor;
import ags.muse.base.actors.RadarActor;
import ags.muse.recon.*;
import ags.util.points.*;

public class MeleeRadar {
    // Final variables
    private final RobotList robots;
    private final Rules rules;

    // State
    private int turndir = 0;

    public MeleeRadar(Rules rules, RobotList robots) {
        this.rules = rules;
        this.robots = robots;
    }

    public void run(RadarActor radar, GunActor gun, MovementActor move) {
        // Set initial turndir to be towards center
        if (turndir == 0) {
            setInitialTurndir();
        }

        if (!robots.haveFoundAll() || robots.getEnemies().size() == 0) {
            // Just spin if we haven't found all bots
            setBlindSpin(radar, gun, move);
        }
        else {
            // Otherwise, decide how to turn the radars
            setTurn(radar, gun, move);
        }
    }

    private void setBlindSpin(RadarActor radar, GunActor gun, MovementActor move) {
        // Turn the bot to speed up radar if not all enemies have been found yet
        gun.setTurnGun(turndir * rules.MAX_TURN_RATE);
        move.setTurnBody(turndir * (rules.GUN_TURN_RATE + rules.MAX_TURN_RATE));
        radar.setTurnRadar(turndir * (rules.RADAR_TURN_RATE + rules.GUN_TURN_RATE + rules.MAX_TURN_RATE));
    }

    private void setTurn(RadarActor radar, GunActor gun, MovementActor move) {
        AbsolutePoint origin = robots.getSelf().getNextLocation();

        int enemyCount = robots.getEnemies().size();
        double[] enemyDirection = new double[enemyCount];
        int i = 0;
        double maxTurn = 0, minTurn = 0;
        boolean sawMax = false, sawMin = false;
        for (EnemyRobot bot : robots.getEnemies()) {
            // Figure out the angles
            double direction = origin.angleTo(bot.getLocation());
            double distance = origin.distance(bot.getLocation());
            double radarturn = normalRelativeAngle(direction - robots.getSelf().getRadarHeading());

            // Store the direction
            enemyDirection[i] = direction;
            i++;

            // Calculate the uncertainty in the angle
            double maxMoved = rules.MAX_VELOCITY*bot.getDataAge();
            double uncertainty = 0;
            if (distance * 2 > maxMoved) {
                uncertainty += 2*Math.asin(maxMoved/(2*distance));
            }
            else {
                uncertainty = Math.PI;
            }

            // Add the uncertainty to the radarturn value, then compare with the highest/lowest
            if (radarturn > 0) {
                radarturn += uncertainty;
                if (radarturn > maxTurn) {
                    maxTurn = radarturn;
                    sawMax = (bot.getDataAge() <= 1);
                }
            }
            else if (radarturn < 0) {
                radarturn -= uncertainty;
                if (radarturn < minTurn) {
                    minTurn = radarturn;
                    sawMin = (bot.getDataAge() <= 1);
                }
            }
        }

        // Decide the direction of radar turn
        // Keep old direction if it is sane to do so
        boolean changedDirection = false;
        double radarTurn;
        if (turndir > 0) {
            if (maxTurn != 0 && !sawMax) {
                radarTurn = maxTurn;
            }
            else {
                turndir = -1;
                radarTurn = minTurn;
                changedDirection = true;
            }
        }
        else {
            if (minTurn != 0 && !sawMin) {
                radarTurn = minTurn;
            }
            else {
                turndir = 1;
                radarTurn = maxTurn;
                changedDirection = true;
            }
        }

        // If the goal turning point leaves us close to another point, just keep turning through to it
        double goalDirection = robots.getSelf().getRadarHeading() + radarTurn;
        double maxOtherTurn = 0;
        for (double otherDirection : enemyDirection) {
            double otherTurn = normalRelativeAngle(otherDirection - goalDirection);
            if (otherTurn == 0) continue;
            // If the other turn is in the same direction, keep turning
            if (((turndir > 0) ? otherTurn : -otherTurn) > 0) {
                maxOtherTurn = turndir * Math.max(turndir * maxOtherTurn, turndir * otherTurn);
            }
        }
        radarTurn += maxOtherTurn;

        // If the radar can't fully turn on it's own, let the gun help out
        double extraTurn = turndir * Math.min(Math.max(radarTurn * turndir - rules.RADAR_TURN_RATE, 0), rules.GUN_TURN_RATE);
        if (changedDirection && extraTurn != 0 && robots.getSelf().getGunHeat() > 3*rules.GUN_COOLING_RATE) {
            // Turn the bot to speed up radar if not firing
            gun.setTurnGun(extraTurn);
        }

        // Turn the radar
        radar.setTurnRadar(radarTurn);
    }

    private void setInitialTurndir() {
        AbsolutePoint center = AbsolutePoint.fromXY(rules.BATTLEFIELD_WIDTH, rules.BATTLEFIELD_HEIGHT);

        // Calculate the necessary radar turn
        double direction = robots.getSelf().getLocation().angleTo(center);
        double radarturn = normalRelativeAngle(direction - robots.getSelf().getRadarHeading());

        // Choose the turn direction
        if (radarturn == 0) {
            turndir = (Math.random() > 0.5) ? 1 : -1;
        }
        else {
            turndir = (radarturn > 0) ? 1 : -1;
        }
    }
}